home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / DTS QT Utilities.Aug-95 / Projects & Test Apps / CompressMovies / CompressMovie.c < prev    next >
Encoding:
Text File  |  1995-04-02  |  16.9 KB  |  445 lines  |  [TEXT/MPCC]

  1. /*
  2.     File:        RecompressMovie.c
  3.  
  4.     Contains:    Functions for recompression of QuickTime movies.
  5.  
  6.     Written by:    DTS
  7.  
  8.     Copyright:    © 1995 by Apple Computer, Inc., all rights reserved.
  9.  
  10.     Change History (most recent first):
  11.  
  12.        <1>         1/30/95    khs        first file
  13.        
  14. */
  15.  
  16.  
  17. // INCLUDES
  18. #include "Movies.h"
  19. #include "MoviesFormat.h"
  20.  
  21. #include "CompressMovie.h"
  22. #include "DTSQTUtilities.h"
  23.     
  24.     
  25. // GLOBALS
  26. static     Boolean                            gFirstTime = true;
  27. static     Boolean                            gShowWindow = true;
  28. static    SCTemporalSettings        gTemporalSettings;
  29. static    SCSpatialSettings            gSpatialSettings;
  30. static    SCDataRateSettings        aDataRateSetting;
  31.  
  32.  
  33. // ______________________________________________________________________
  34. // FUNCTIONS
  35.  
  36. // ______________________________________________________________________
  37. // SetFirstRecompressState is a simple wrapper for the global so that we could control from the outside
  38. // when we have done the first pass, and after that the code in RecompressMovieFile should branch off
  39. // and not display the standard compression dialog boxes. The reason it's a function is that we want to 
  40. // maybe change something later, and it's easier this way than  chasing for globals.
  41. pascal void SetFirstRecompressState(Boolean state)
  42. {
  43.     gFirstTime = state;
  44. }
  45.  
  46.  
  47. // ______________________________________________________________________
  48. // RecompressMovieFile is a long and windy function, a lot of it is from the ConvertToMovie Jr. 
  49. // sample (SDK CDs). Many parts have been extracted into the DTSQTLibrary file. Anyway, 
  50. // this function could be taken out and implemented in other tools and parts as it's very much 
  51. // self-contained (if you add the DTSQTUtilities files to your project as well).
  52.  
  53. pascal OSErr RecompressMovieFile(FSSpec* theMovieFile)
  54. {
  55.     OSErr                         anErr = noErr;
  56.     Boolean                        abort;
  57.     
  58.     ComponentInstance     ci = NULL;
  59.     
  60.     long                            ciFlags;
  61.     long                            nFrames;
  62.     long                            aFrameNum;
  63.     TimeValue                    currentMovieTime;
  64.  
  65.     short                        aMovieRefNum;
  66.     FSSpec                         newFileFSSpec;
  67.  
  68.     Movie                        aSourceMovie = NULL;
  69.     Rect                             aMovieRect;
  70.     GWorldPtr                    srcGWorld = NULL;
  71.     ImageDescription        **anImageDescription;
  72.     ImageSequence            anImageSequence;
  73.     Movie                        aDestinationMovie = NULL;
  74.     Track                        aDestinationTrack = NULL;
  75.     Media                        aDestinationMedia = NULL;
  76.     
  77. // if we use a window, the following variables are used
  78.     Point                            where;
  79.     WindowRef                progressWindow;
  80.     
  81.     
  82.     // Open the standard compression component
  83.     ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType); DebugAssert(ci != NULL);
  84.     if(ci == NULL) return couldntGetRequiredComponent; 
  85.     
  86.     // Adjust the user settings, we will operate in 32-bit depth (always), and make it possible to leave the
  87.     // data rate field zero (this indicates to use the existing data rate in the movie)
  88.     SCGetInfo(ci, scPreferenceFlagsType, &ciFlags);
  89.     ciFlags &=~scShowBestDepth;
  90.     ciFlags |= scAllowZeroFrameRate;
  91.     SCSetInfo(ci, scPreferenceFlagsType, &ciFlags);
  92.     
  93.     // Open the movie file, make sure it contains a movie.
  94.     anErr = OpenMovieFile(theMovieFile, &aMovieRefNum, 0); ReturnIfError(anErr);
  95.     
  96.     anErr = NewMovieFromFile(&aSourceMovie, aMovieRefNum, NULL, NULL, newMovieActive, NULL);  ReturnIfError(anErr);
  97.     
  98.     CloseMovieFile(aMovieRefNum);
  99.     
  100.     // If the movie does not contain any video tracks, bail out (we don't re-compress sound or other tracks at this point 
  101.     // of time.
  102.     if(! QTUMediaTypeInTrack(aSourceMovie, VideoMediaType))
  103.     {
  104.         DebugAssert("No video tracks in movie");
  105.         return     invalidMovie;
  106.     }
  107.     
  108.     // Count the amount of video frames in the movie
  109.     nFrames = QTUCountMediaSamples(aSourceMovie, VideoMediaType); DebugAssert(nFrames != -1);
  110.     
  111.     // Given the movie's bounding rect, create a 32-bit GWorld we will use for rendering movie frames and for possible test images.
  112.     {
  113.         CGrafPtr        aSavedPort = NULL;
  114.         GDHandle        aGDHandle = NULL ;
  115.         PicHandle         aPicHandle = NULL; 
  116.         
  117.         GetMovieBox(aSourceMovie, &aMovieRect);
  118.         
  119.         anErr = NewGWorld(&srcGWorld, 32, &aMovieRect, NULL, NULL, 0);   DebugAssert(anErr == noErr);
  120.         if(anErr != noErr) goto CleanupMemory;
  121.         
  122.         aPicHandle = GetMoviePosterPict(aSourceMovie);     // don't need to test if the PicHandle was created or not.
  123.         
  124.         if(aPicHandle)
  125.         {
  126.             GetGWorld(&aSavedPort, &aGDHandle);
  127.             SetGWorld(srcGWorld, NULL);
  128.             EraseRect(&aMovieRect);
  129.             
  130.             DrawPicture(aPicHandle, &aMovieRect);
  131.             KillPicture(aPicHandle);
  132.             SetGWorld(aSavedPort, aGDHandle);
  133.             
  134.             // Use the image now in the GWorld as the initial image inside the dialog box for compression.
  135.             anErr = SCSetTestImagePixMap(ci, srcGWorld->portPixMap, NULL, 0);  DebugAssert(anErr == noErr);
  136.             if(anErr != noErr) goto CleanupMemory;
  137.         }
  138.     }    
  139.     // Set default settings for the compression dialog, and also get the temporal settings we need for further
  140.     // calculations.
  141.  
  142.     if(gFirstTime)
  143.     {
  144.         anErr = SCDefaultPixMapSettings(ci, srcGWorld->portPixMap, true);   DebugAssert(anErr == noErr);
  145.         if(anErr != noErr) goto CleanupMemory;
  146.  
  147.         anErr = SCGetInfo(ci, scTemporalSettingsType, &gTemporalSettings);   DebugAssert(anErr == noErr);
  148.         if(anErr != noErr) goto CleanupMemory;
  149.         
  150.         gTemporalSettings.frameRate = 0;
  151.         
  152.         anErr = SCSetInfo(ci, scTemporalSettingsType, &gTemporalSettings);   DebugAssert(anErr == noErr);
  153.         if(anErr != noErr) goto CleanupMemory;
  154.         
  155.     }    
  156.     
  157.  
  158.     
  159.     // Clear out the default frame rate at the first pass selected by the standard compression. 0 means that we
  160.     // need to use the data rate of the movie.
  161.     if(gFirstTime)
  162.     {
  163.         // ask the first time from the end user about the default settings we will use with any other movies passed
  164.         // along to this batch of movies (dragged to the app).
  165.         anErr = SCRequestSequenceSettings(ci); DebugAssert(anErr == noErr);
  166.         if(anErr != noErr) return anErr; // eventually scUserCancelled as the error
  167.     
  168.         // Get a copy of the temporal settings we got from the user interaction, we need the values later for
  169.         // other calculations (new frame amount and so on).
  170.     
  171.         anErr = SCGetInfo(ci, scTemporalSettingsType, &gTemporalSettings);  DebugAssert(anErr == noErr);
  172.         if(anErr != noErr) goto CleanupMemory;
  173.         
  174.         anErr = SCGetInfo(ci, scSpatialSettingsType, &gSpatialSettings); DebugAssert(anErr == noErr);
  175.         if(anErr != noErr) goto CleanupMemory;
  176.     }
  177.  
  178.     if(!gFirstTime)    // We need to set the temporal and the spatial settings the following times.
  179.     {
  180.         anErr = SCSetInfo(ci, scTemporalSettingsType, &gTemporalSettings);   DebugAssert(anErr == noErr);
  181.         if(anErr != noErr) goto CleanupMemory;
  182.         
  183.         anErr = SCSetInfo(ci, scSpatialSettingsType, &gSpatialSettings); DebugAssert(anErr == noErr);
  184.         if(anErr != noErr) goto CleanupMemory;  
  185.     }
  186.     
  187.     // Calculate the max sound rate, so we know the overall data rate for the video (total = video + sound).
  188.     {
  189.         long soundDataRate;
  190.     
  191.     if(gFirstTime)
  192.     {    
  193.         anErr = SCGetInfo(ci, scDataRateSettingsType, &aDataRateSetting);  DebugAssert(anErr == noErr);
  194.         if(anErr != noErr) goto CleanupMemory;
  195.     }
  196.         
  197.         if(aDataRateSetting.dataRate)
  198.         {
  199.             anErr = QTUCountMaxSoundRate(aSourceMovie, &soundDataRate);  DebugAssert(anErr == noErr);
  200.             if(anErr != noErr) goto CleanupMemory;
  201.         
  202.             aDataRateSetting.dataRate  -= soundDataRate;
  203.         }
  204.         
  205.         anErr = SCSetInfo(ci, scDataRateSettingsType, &aDataRateSetting);  DebugAssert(anErr == noErr);
  206.         if(anErr != noErr) goto CleanupMemory;
  207.     }
  208.     
  209.     // Calculate the new amount of frames based on the possible new re-defined frame rate.
  210.     if(gTemporalSettings.frameRate)
  211.         nFrames = QTUGetMovieFrameCount(aSourceMovie, gTemporalSettings.frameRate);
  212.     
  213.     // If we want to show a windows when processing the movie, do this here...
  214.     if(gShowWindow)
  215.     {
  216.         Rect aRect = aMovieRect;
  217.         where.h = where.v = -2;
  218.         
  219.         anErr = SCPositionRect(ci, &aRect, &where);  ReturnIfError(anErr);
  220.         
  221.         progressWindow = NewCWindow(0,&aRect, theMovieFile->name, true, 0, (WindowPtr)-1, false, 0);
  222.     }
  223.     
  224.     // Create a new file for the re-compressed movie.
  225.     {
  226.         Str255 newFileName;
  227.         
  228.         // First fix the name FSSpec and name for this new movie.
  229.         BlockMove(theMovieFile->name, newFileName, sizeof(theMovieFile->name));     
  230.         newFileName[++newFileName[0]] = '*';            // add this character to the beginning of the new file name
  231.     
  232.         // Then create a new FSSpec.
  233.         anErr = FSMakeFSSpec(theMovieFile->vRefNum, theMovieFile->parID, newFileName, &newFileFSSpec);
  234.     // ••• check this out later    if(anErr != noErr || anErr != fnfErr ) return anErr;
  235.         
  236.         // Then create a movie file.
  237.         anErr = CreateMovieFile(&newFileFSSpec, 'TVOD', 0, createMovieFileDeleteCurFile, &aMovieRefNum, &aDestinationMovie); DebugAssert(anErr == noErr);
  238.         if(anErr != noErr) goto CleanupGeneral;
  239.     }
  240.         
  241.     // Copy and create various media and tracks for the new movie.
  242.     {
  243.         MatrixRecord aMatrix;
  244.         // Create a new video movie track with the same dimensions as the entire source movie.
  245.         aDestinationTrack = NewMovieTrack(aDestinationMovie, (long)(aMovieRect.right - aMovieRect.left) << 16,
  246.                                                                     (long)(aMovieRect.bottom - aMovieRect.top) << 16, 0);
  247.         
  248.             
  249.         // Create a media for the new track with the same time scale as the source movie. If the time scales are the same,
  250.         // we don't need to adjust the time scales with scale conversions.
  251.         aDestinationMedia = NewTrackMedia(aDestinationTrack, VIDEO_TYPE, GetMovieTimeScale(aSourceMovie), 0, 0);
  252.         anErr = GetMoviesError();DebugAssert(anErr == noErr);
  253.         if(anErr != noErr) goto CleanupGeneral;
  254.         
  255.         // Copy the user data and settings from the source to the destination movie.
  256.         CopyMovieSettings(aSourceMovie, aDestinationMovie);
  257.         
  258.         // Set the movie matrix to identity and clear the movie clip region so that conversion process transforms and composites
  259.         // *all* video tracks into one untransformed video track.
  260.         SetIdentityMatrix(&aMatrix);
  261.         SetMovieMatrix(aDestinationMovie, &aMatrix);
  262.         SetMovieClipRgn(aDestinationMovie, NULL);
  263.         
  264.         // Prepare for adding frames to the movie.
  265.         anErr = BeginMediaEdits(aDestinationMedia); DebugAssert(anErr == noErr);
  266.         if(anErr != noErr) goto CleanupGeneral;
  267.     }
  268.     
  269.     // Start a compression sequence using the parameters chosen earlier (not these are true for all the other movies passed
  270.     // along with the AE. Pass nil for the source rect to use the entire image. We will get an imagedescription as well. Note
  271.     // that the image description handle is disposed by SCCompressSequenceEnd.
  272.     anErr = SCCompressSequenceBegin(ci, srcGWorld->portPixMap, NULL, &anImageDescription); DebugAssert(anErr == noErr);
  273.     if(anErr != noErr) goto CleanupGeneral;
  274.         
  275.     // Clear out the GWorld and set the movie to draw into this one.
  276.     SetGWorld(srcGWorld, NULL);
  277.     EraseRect(&srcGWorld->portRect);
  278.     SetMovieGWorld(aSourceMovie, srcGWorld, GetGWorldDevice(srcGWorld));
  279.     
  280.     currentMovieTime = 0;            // set current time value to beginning of movie
  281.         
  282.     
  283.     // Loop through all the interesting times counted earlier
  284.     for(aFrameNum = 0; aFrameNum < nFrames; aFrameNum++)
  285.     {
  286.         short        syncFlag;
  287.         TimeValue    duration;
  288.         long            dataSize;
  289.         Handle        compressedData;
  290.         
  291.         // Abort if the end user clicked the mouse or pressed a key.
  292.         {
  293.             EventRecord anEvent;
  294.             
  295.             abort = false;
  296.             if(EventAvail(keyDownMask | mDownMask, &anEvent))
  297.             {
  298.                 abort = true;
  299.                 break;
  300.             }
  301.         }
  302.         
  303.         // Get the next frame from the movie.
  304.         {
  305.             // If we are resampling the movie, step to the next frame
  306.             if(gTemporalSettings.frameRate)
  307.             {
  308.                 // This could could be much smarter about its calculations. The srcMovie duration and destination movie
  309.                 // frame durations are both constant and could be calculated outside this loop.
  310.                 
  311.                 long dur = GetMovieDuration(aSourceMovie);
  312.                 currentMovieTime = aFrameNum * dur / (nFrames - 1);
  313.                 duration = dur / nFrames;
  314.             }
  315.             else
  316.             {
  317.                 short flags = nextTimeMediaSample;
  318.                 OSType whichMediaType = VIDEO_TYPE;
  319.                 
  320.                 // If this is the first frame, include the frame we are currently on.
  321.                 if(aFrameNum == 0)
  322.                     flags |= nextTimeEdgeOK;
  323.                 
  324.                 // If we are maintaining the frame durations of the source movie, skip to the next interesting
  325.                 // time and get the duration of that frame.
  326.                 GetMovieNextInterestingTime(aSourceMovie, flags, 1, &whichMediaType, currentMovieTime, 0, ¤tMovieTime, &duration);
  327.             }
  328.             
  329.             SetMovieTimeValue(aSourceMovie, currentMovieTime);
  330.             MoviesTask(aSourceMovie, 0); MoviesTask(aSourceMovie,0); MoviesTask(aSourceMovie,0);
  331.         } //end stepping to next frame from the movie
  332.  
  333.         {
  334.             // If data rate constraining is being done, tell Standard Compression the duration of the current frame in
  335.             // milliseconds. We only need to do this if the frames have variable durations.
  336.             SCDataRateSettings datarate;
  337.             if(!SCGetInfo(ci, scDataRateSettingsType, &datarate))
  338.             {
  339.                 datarate.frameDuration = duration * 1000 / GetMovieTimeScale(aSourceMovie);
  340.                 SCSetInfo(ci, scDataRateSettingsType, &datarate);
  341.             }
  342.         }
  343.         
  344.         // Compress the frame, compressedData will hold a handle to the newly compressed image data. dataSize is
  345.         // the size of the compressed data, which will usually be different than the size of the compressData handle.
  346.         // syncFlag is a value that is a key frame. Note that we don't have to dispose the compressedData handle.
  347.         // It will be disposed for us when we call SCCompressSequenceEnd.
  348.         anErr = SCCompressSequenceFrame(ci,srcGWorld->portPixMap, &aMovieRect, &compressedData, &dataSize, &syncFlag);
  349.         ReturnIfError(anErr);
  350.         
  351.         // Append the compressed image data to the media.
  352.         anErr = AddMediaSample(aDestinationMedia, compressedData, 0, dataSize, duration,
  353.                                                 (SampleDescriptionHandle)anImageDescription, 1, syncFlag, NULL); DebugAssert(anErr == noErr);
  354.         if(anErr != noErr) goto CleanupGeneral;
  355.         
  356.         // Decompress the compressed frame into the progress window.
  357.         if(gShowWindow)
  358.         {
  359.             char hState;
  360.             
  361.             SetGWorld((CGrafPtr)progressWindow, NULL);     // set port to progress window
  362.             
  363.             // If this is the first frame, start up a decompression sequence.
  364.             if(aFrameNum == 0)
  365.             {
  366.                 anErr = DecompressSequenceBegin(&anImageSequence, anImageDescription, NULL, NULL, &aMovieRect,
  367.                                                                         NULL, ditherCopy, NULL, 0, codecNormalQuality, anyCodec);  DebugAssert(anErr == noErr);
  368.                 if(anErr != noErr) goto CleanupGeneral;
  369.             }
  370.             
  371.             // Save the locked state of the compressed data and then lock it. We want it locked but standard compression may or 
  372.             // may not.
  373.             hState = HGetState(compressedData);
  374.             HLock(compressedData);
  375.             
  376.             // Decompress the frame to the progress window. Note that we StripAddress the compressedData pointer 
  377.             // because it must be 32-bit clean.
  378.             anErr = DecompressSequenceFrame(anImageSequence, StripAddress(*compressedData), 0, NULL, NULL); DebugAssert(anErr == noErr);
  379.             if(anErr != noErr) goto CleanupGeneral;
  380.             
  381.             // Restore the locked state of the data handle.
  382.             HSetState(compressedData, hState);
  383.             if(anErr != noErr) return anErr;
  384.         } // end gShowWindow
  385.     } // end big for loop!
  386.  
  387.     // Close the compression sequence. This will dispose of the image description and compressed data handles allocated by
  388.     // SCCompressSequenceBegin.
  389.     SCCompressSequenceEnd(ci);
  390.     
  391.     // Close the decompression sequence. Note that this is an Image Compression Manager call, not Standard Compression.
  392.     CDSequenceEnd(anImageSequence);
  393.     
  394.     // Copy all sound tracks from the source to the destination movie. Note that we are currently not copying any other
  395.     // tracks here (text tracks, alternate tracks and so on). We need to provide more options here later.
  396.     anErr = QTUCopySoundTracks(aSourceMovie, aDestinationMovie);  DebugAssert(anErr == noErr);
  397.     if(anErr != noErr) goto CleanupGeneral;
  398.         
  399.     // We have now finished compressing video data. Next, make this data part of our movie.
  400.     if(aDestinationTrack)    // we have a valid destination track
  401.     {
  402.         short resID = 128;
  403.         
  404.         anErr = EndMediaEdits(aDestinationMedia); ReturnIfError(anErr);
  405.         
  406.         // Insert the newly created media into the newly created track at the beginning of the track and lasting
  407.         // for the entire duration of the media. The media rate is 1.0 for normal playback rate.
  408.         InsertMediaIntoTrack(aDestinationTrack, 0, 0, GetMediaDuration(aDestinationMedia), fixed1);
  409.         
  410.         // Add the movie resource into the destination movie file.
  411.         anErr = AddMovieResource(aDestinationMovie, aMovieRefNum, &resID, "\pMovie 1"); DebugAssert(anErr == noErr);
  412.         if(anErr != noErr) goto CleanupGeneral;
  413.         
  414.         // Flatten the movie file just created for performance purposes. Make it crossplatform at the same time.
  415.         CloseMovieFile(aMovieRefNum);    // note: we need to close this file as we will delete this and swap it with a temp file 
  416.                                                             // in the function below.
  417.         anErr = QTUFlattenMovieFile(aDestinationMovie, &newFileFSSpec);
  418.     }
  419.         
  420.  
  421. // CleanupGeneral is our main entry point if we want to get rid of the window and clean up memory.    
  422. CleanupGeneral:            
  423.     // POSTFIX
  424.     
  425.     // Get Rid of the progress window
  426.     if(gShowWindow)
  427.     {
  428.         CloseWindow(progressWindow);
  429.         progressWindow = NULL;
  430.     }
  431.  
  432. // CleanUpMemory is the entry point if we don't have the window displayed, but we still want to clean up memory.
  433. CleanupMemory:    
  434.     // Get Rid of any buffers, handles, and other resources allocated earlier
  435.     if(srcGWorld) DisposeGWorld(srcGWorld);        // Get rid of the GWorld handle.
  436.     
  437.     // Clear the test image because we disposed the pixmap it depended upon.
  438.     SCSetTestImagePixMap(ci, NULL, NULL, 0);
  439.     
  440.     CloseComponent(ci);                                        // Close the component after use.
  441.  
  442.     return anErr;
  443. }
  444.  
  445. // THE END